# 服务端
import wx
# import socket
from socket import *
import threading  # 多线程
from concurrent.futures import ThreadPoolExecutor  # 线程池管理

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title = '聊天室-服务端',
                         size = (450, 670), pos = (0, 50))
        self.init_server()
        # 服务端界面
        panel = wx.Panel(self)
        start_server_btn = wx.Button(panel, label = '启动服务器',
                                     size = (200, 40), pos = (10, 10))
        save_text_btn = wx.Button(panel, label='保存聊天记录',
                                     size = (200, 40), pos = (220, 10))
        # 显示当前的聊天信息，只读
        self.message_text = wx.TextCtrl(panel, size = (410, 400), pos = (10, 60),
                    style = wx.TE_MULTILINE | wx.TE_READONLY)
        # 为按钮绑定事件方法
        start_server_btn.Bind(wx.EVT_BUTTON, self.start_server)
        save_text_btn.Bind(wx.EVT_BUTTON, self.save_text)
        self.Show()

    # 设置socket基本信息
    def init_server(self):
        # 创建服务端socket对象
        self.server_socket = socket()
        # 接收任何地址的连接，端口号是8888
        self.server_socket.bind(('0.0.0.0', 8888))
        # 设置最大连接数5
        self.server_socket.listen(5)
        # 保存所有客户端的线程
        self.thread_dict = {}
        # 创建线程池，最大线程数量为10
        self.pool = ThreadPoolExecutor(max_workers = 10)

    # 启动服务端
    def start_server(self, event):
        # 属于主线程
        print('启动服务端...')
        # 创建线程
        main_thread = threading.Thread(target = self.main_thread_fun)
        # 设置为守护线程
        main_thread.daemon = True
        # 启动线程
        main_thread.start()

    # 使用守护线程循环接收客户端的连接
    def main_thread_fun(self):
        # 有多少个客户端发起连接，就会循环多少遍
        while True:
            # 因为循环的第一行代码是接收新连接，所以不能收到已经连接的用户的二次消息
            # 建立客户端连接，接收客户端传递的数据
            client_socket, addr = self.server_socket.accept()  # 等待
            # 接收数据
            client_name = client_socket.recv(1024).decode('utf-8')
            # 再创建一个独立线程来接收和回复后续的聊天的内容
            client_thread = ClientThread(client_socket, client_name, self)
            # 保存客户端的线程到字典
            self.thread_dict[client_name] = client_thread
            # 提交到线程池
            self.pool.submit(client_thread.run)
            msg = f'【服务器通知】欢迎【{client_name}】进入聊天室'
            # 群发欢迎消息
            self.send_to_all(msg)

    # 群发消息
    def send_to_all(self, msg):
        # 追加消息到服务端的信息框
        self.message_text.AppendText(msg + '\n')
        # 群发
        for client_name, client_thread in self.thread_dict.items():
            client_thread.client_socket.send(msg.encode('utf-8'))

    # 保存聊天记录
    def save_text(self, event):
        print('保存聊天记录...')
        # 持久，从消息显示框中获取所有内容保存到文件中
        with open('record.log', 'a', encoding = 'utf-8') as file:
            file.write(self.message_text.GetValue())

# 自定义客户端线程类
class ClientThread(threading.Thread):
    # 参数：客户端socket、客户端名字、服务端的窗体对象
    def __init__(self, client_socket, client_name, server):
        super().__init__()
        self.client_socket = client_socket
        self.client_name = client_name
        self.server = server
        self.is_client_connected = True  # 目标客户端是否已连接

    # 重写run方法：表示线程需要执行的任务
    def run(self):
        while self.is_client_connected:
            # 接收消息
            msg = self.client_socket.recv(1024).decode('utf-8')
            # 判断消息是否为离线消息
            if msg == f'{self.client_name}下线了':
                # 群发离线消息
                self.server.send_to_all(f'【服务器通知】【{self.client_name}】离开了聊天室')
                # 修改状态为未连接
                self.is_client_connected = False
            else:
                # 群发给所有用户，例如"张三：你好"
                self.server.send_to_all(f'{self.client_name}：{msg}')
        # 循环结束，表示该客户端离线了，从字典中移除客户端socket
        self.server.thread_dict.pop(self.client_name)
        # 关闭客户端socket
        self.client_socket.close()

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()